package statemachine

import (
	"fmt"
	"sync"
)

var (
	_ StateMachine = CreateStateMachine()
)

type StateMachineImpl struct {
	chart        map[StateMachineState]map[StateMachineStateEvent]StateMachineState
	listeners    []StateMachineListener
	listenersTmp []StateMachineListener
	l            sync.Mutex
}

func (m *StateMachineImpl) AddTranscation(old StateMachineState, evt StateMachineStateEvent, next StateMachineState) {
	entry := m.chart[old]
	if entry == nil {
		entry = make(map[StateMachineStateEvent]StateMachineState)
		m.chart[old] = entry
	}
	entry[evt] = next
}

func (m *StateMachineImpl) GetNextState(old StateMachineState, evt StateMachineStateEvent) (StateMachineState, error) {
	entry := m.chart[old]
	if entry == nil {
		return StateMachineStateUnknow, fmt.Errorf("Cannot find next state:[old state:%s, state event: %s]", string(old), string(evt))
	}

	next := entry[evt]
	if next == StateMachineStateUnknow {
		return StateMachineStateUnknow, fmt.Errorf("Cannot find next state:[old state:%s, state event: %s]", string(old), string(evt))
	}

	return next, nil
}

func (m *StateMachineImpl) AddListener(l StateMachineListener) {
	m.l.Lock()
	defer m.l.Unlock()

	m.listeners = append(m.listeners, l)
}

func (m *StateMachineImpl) RemoveListener(l StateMachineListener) {
	m.l.Lock()
	defer m.l.Unlock()

	if len(m.listeners) == 0 {
		return
	}

	tmp := make([]StateMachineListener, 0, len(m.listeners)-1)
	for _, v := range m.listeners {
		if v == l {
			continue
		}
		tmp = append(tmp, v)
	}
	m.listeners = tmp
}

func (m *StateMachineImpl) FireBeforeListener(old StateMachineState, evt StateMachineStateEvent, next StateMachineState, args ...any) error {
	m.listenersTmp = nil

	m.l.Lock()
	m.listenersTmp = make([]StateMachineListener, 0, len(m.listeners))
	m.listenersTmp = append(m.listenersTmp, m.listeners...)
	m.l.Unlock()

	var err error
	for _, l := range m.listenersTmp {
		if err = l.Before(old, evt, next, args...); err != nil {
			return fmt.Errorf("Unhandled exception while calling listener:%s before state changing.[current state: %s, event: %s next state: %s]",
				l.GetName(), string(old), string(evt), string(next))
		}
	}

	return err
}

func (m *StateMachineImpl) FireAfterListener(prev StateMachineState, evt StateMachineStateEvent, curr StateMachineState, args ...any) error {
	m.listenersTmp = nil

	m.l.Lock()
	m.listenersTmp = make([]StateMachineListener, 0, len(m.listeners))
	m.listenersTmp = append(m.listenersTmp, m.listeners...)
	m.l.Unlock()

	var err error
	for _, l := range m.listenersTmp {
		if err = l.After(prev, evt, curr, args...); err != nil {
			return fmt.Errorf("Unhandled exception while calling listener:%s after state changing.[previous state: %s, event: %s current state: %s]",
				l.GetName(), string(prev), string(evt), string(curr))
		}
	}

	return err
}

func CreateStateMachine() *StateMachineImpl {
	return &StateMachineImpl{
		chart:        map[StateMachineState]map[StateMachineStateEvent]StateMachineState{},
		listeners:    make([]StateMachineListener, 0, 3),
		listenersTmp: make([]StateMachineListener, 0, 3),
		l:            sync.Mutex{},
	}
}
